/*
*Copyright (c) Nikhil Jain.
*Project ICARUS, communications and 
*artificial intelligence.
*/



package javadrone.util;

//~--- JDK imports ------------------------------------------------------------

import java.awt.*;
import java.awt.event.*;

//import Logging.LogRunner;
import java.util.Vector;

import javax.media.*;
import javax.media.datasink.*;
import javax.media.format.*;
import javax.media.protocol.*;

import javax.swing.JButton;
import javax.swing.JOptionPane;

/**
 *
 * Control the ending of the program prior to closing the data sink
 */
class TheDataSinkListener implements DataSinkListener {
    boolean endOfStream = false;

    // Flag the ending of the data stream
    public void dataSinkUpdate(DataSinkEvent event) {
        if (event instanceof javax.media.datasink.EndOfStreamEvent) {
            endOfStream = true;
        }
    }

    /**
     * Cause the current thread to sleep if the data stream is still available.
     * This makes certain that JMF threads are done prior to closing the data sink
     * and finalizing the output file
     */
    public void waitEndOfStream(long checkTimeMs) {
        while (!endOfStream) {
            try {

                // Thread.currentThread().sleep(checkTimeMs);
                Thread.sleep(checkTimeMs);
            } catch (InterruptedException ie) {

                // exception handling here
            }
        }
    }
}


/**
 * Records audio and video from a usb camera and saves it to disk. Essentially what is happening
 * is we are creating two video streams and an audio stream. We use two video streams
 * so one can be sent to the file, and the other can be sent to the gui to show the
 * user what they are currently recording. This class is configured to use a certain
 * audio and video format, but this can easily be changed to fit dirrenet needs.
 * This class was created with
 * help from http://www.codeproject.com/KB/audio-video/Java_Video_Capture.aspx.
 * Please refer to that site for more information.
 * @author dvargo
 */
public class VideoRecorder {

    /**
     * takes a DataSource as input and renders the output to a specified destination
     */
    DataSink dataSink = null;

    /**
     *
     */
    TheDataSinkListener dataSinkListener = null;

    /**
     * The source of the video stream
     */
    CaptureDeviceInfo device = null;

    /**
     * The form for the video and other gui compoenents
     */
    Frame frm = null;

    /**
     * The location of media content
     */
    MediaLocator ml = null;

    /**
     * MediaHandler for rendering and controlling time based media data
     */
    Player player = null;

    /**
     * The Processor processes data and creates an output in the destination
     * format required
     */
    Processor processor = null;

    /**
     * A reference to the Starter class.
     */
    VideoStarter theForm = null;

    /**
     * The screen that will display the video as it is being recorded
     */
    Component videoScreen = null;

    /**
     * Used to determine if something went wrong
     */
    boolean error = false;

    /**
     * Basic Constructor to begin recording video to a specified file path. This
     * constructor initalizes everything needed to begin recording.
     * @param saveFileTo The path and file name of where to save the video.
     * @param inForm Reference to the Starter class that initatied this recording.
     */
    public VideoRecorder(String saveFileTo, VideoStarter inForm) {

        // theForm = inForm;
        try {

            // gets a list of devices how support the given video format
            Vector deviceList = CaptureDeviceManager.getDeviceList(new YUVFormat());

            System.out.println(deviceList);

            // if we couldnt find anything to record from
            if (deviceList.size() == 0) {

                // LogRunner.dialogMessage(this.getClass(),"No video capture devices could be found.");
                error = true;
                System.out.println("Error...");

                return;
            }

            // get video device - the first one is almost always the only available camera
            device = (CaptureDeviceInfo) deviceList.firstElement();
            ml     = device.getLocator();

            // create a source from the device
            DataSource ods = null;

            ods = Manager.createDataSource(ml);

            /*
             * Clone the video source so it can be displayed and used to capture
             * the video at the same time. Trying to use the same source for two
             * purposes would cause a "source is in use" error
             */
            DataSource cloneableDS = Manager.createCloneableDataSource(ods);
            DataSource PlayerVidDS = cloneableDS;

            // The video capture code will use the clone which is controlled by the player
            DataSource CaptureVidDS = ((javax.media.protocol.SourceCloneable) cloneableDS).createClone();

            /*
             * Display video by starting the player on the source clonable data source
             * the clones are fed data stopping the player will stop the video flow
             * to the clone data source
             */
            player = Manager.createRealizedPlayer(PlayerVidDS);
            player.start();

            // get an audio device and create an audio data source
            deviceList = CaptureDeviceManager.getDeviceList(new javax.media.format.AudioFormat(null));
            device     = (CaptureDeviceInfo) deviceList.firstElement();
            ml         = device.getLocator();

            DataSource audioDataSource = Manager.createDataSource(ml);

            // merge audio and video data sources
            DataSource mixedDataSource = null;
            DataSource dsArray[]       = new DataSource[2];

            dsArray[0] = CaptureVidDS;    // this is a cloned datasource and is controlled by the master clonable data source
            dsArray[1] = audioDataSource;

            try {
                mixedDataSource = javax.media.Manager.createMergingDataSource(dsArray);
            } catch (Exception e) {

                // exception handling here
                error = true;
            }

            // setup output file format to msvideo
            FileTypeDescriptor outputType = new FileTypeDescriptor(FileTypeDescriptor.MSVIDEO);

            // setup output video and audio data format
            Format outputFormat[] = new Format[2];

            // outputFormat[0] = new VideoFormat(VideoFormat.RGB);
            outputFormat[0] = new VideoFormat(VideoFormat.YUV);
            outputFormat[1] = new AudioFormat(AudioFormat.LINEAR);

            // create a new processor
            ProcessorModel processorModel = new ProcessorModel(mixedDataSource, outputFormat, outputType);

            try {
                processor = Manager.createRealizedProcessor(processorModel);
            } catch (Exception e) {

                // exception handling here
                error = true;
            }

            try {

                // get the output of the processor to be used as the datasink input
                DataSource source = processor.getDataOutput();

                // create a File protocol MediaLocator with the location of the file to which bits are to be written
                MediaLocator mediadestination = new MediaLocator("file:" + saveFileTo);

                // create a datasink to create the video file
                dataSink = Manager.createDataSink(source, mediadestination);

                // create a listener to control the datasink
                dataSinkListener = new TheDataSinkListener();
                dataSink.addDataSinkListener(dataSinkListener);
                dataSink.open();

                // now start the datasink and processor
                dataSink.start();
                processor.start();
            } catch (Exception e) {

                // exception handling here
                error = true;
            }

            // set up the gui
            createGui();
        } catch (Exception e) {

            // LogRunner.getLogger().warning("Error recording video - " + e.getMessage());
            e.printStackTrace();
            error = true;
        }
    }

    /**
     * Flag that determines if something went wrong
     * @return True if something did go wrong, false if everything is fine.
     */
    public boolean getError() {
        return error;
    }

    /**
     * Creates the gui used to display what is currently being recorded.
     */
    private void createGui() {
        videoScreen = player.getVisualComponent();
        frm         = new Frame("Recording");
        frm.setSize(300, 300);
        frm.setLayout(new BorderLayout());
        frm.add(videoScreen, BorderLayout.CENTER);

        JButton close = new JButton("Click Here when done");

        frm.add(close, BorderLayout.SOUTH);
        close.setVisible(true);
        close.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(ActionEvent e) {

                // Stop the processor doing the movie capture first
                processor.stop();
                processor.close();

                // Closing the processor will end the data stream to the data sink.
                // Wait for the end of stream to occur before closing the datasink
                dataSinkListener.waitEndOfStream(10);
                dataSink.close();

                // stop and close the player which closes the video data source
                player.stop();
                player.close();
                theForm.done();

                // dispose of the frame and close the application
                frm.setVisible(false);
            }
        });
        frm.setAlwaysOnTop(true);
        frm.setUndecorated(true);
        frm.setLocationRelativeTo(theForm.mainWindow);
        frm.setVisible(true);
    }
}


//~ Formatted by Jindent --- http://www.jindent.com
